#include <game/flow/Manager.h>
#include <game/flow/EntryDefinition.h>
#include <game/flow/EntryExitDefinition.h>
#include <game/flow/Selector.h>
#include <game/flow/TriggerEntry.h>
#include <game/Root.h>

#include <string>
#include <resource/Manager.h>
#include <xml/Stream.h>
#include <system/assert.h>
#include <Gluck.h>
#include <boost/bind.hpp>

ARELLO_GAME_NAMESPACE_DECL_START
namespace flow {

Manager::Manager()
	: root(0)
{
}

Manager::~Manager()
{
	for (Entries::iterator i = entries.begin(); i != entries.end(); ++i)
		delete i->second;
}

bool Manager::on_attribute(xml::Provider * prov, const std::string & name, const std::string & value)
{
	if (name == "Start")
		return converter::convert(value, &this->start);

	return false;
}

bool Manager::on_node(xml::Provider * prov, const std::string & name)
{
	if (name == "Entry")
	{
		std::auto_ptr<EntryDefinition> entry(new EntryDefinition());
		if (!prov->parse_object(entry->GetLoader()))
			return false;

		std::string entryName = entry->GetName();
		
		std::pair<Entries::iterator, bool> insertResult =
			this->entries.insert(std::make_pair(entryName, entry.release()));
		
		if (!insertResult.second)
		{
			LIBA_FAIL("Duplicate key");
			return false;
		}

		return true;
	}
	if (name == "Trigger")
	{
		std::auto_ptr<TriggerEntry> entry(new TriggerEntry(*this));

		if (!prov->parse_object(entry->GetLoader()))
			return false;

		this->triggers.push_back(entry.release());
		return true;
	}

	return false;
}

void checkLinks(Manager::Entries& entries)
{
	for (Manager::Entries::const_iterator i = entries.begin(); i != entries.end(); ++i)
	{
		for (std::vector<EntryExitDefinition*>::iterator j = i->second->ExitsBegin();
			j != i->second->ExitsEnd(); ++j)
		{
			std::string name = (*j)->GetEntry();
			if (!name.empty() && name[0] == '$')
			{
				name.erase(0, 1);
			}
			if (!name.empty() && name[0] == '#')
			{
				LIBA_ASSERT(name == "#Advance");
			}
			else
			{
				Manager::Entries::iterator k = 
					entries.find(name);
	
				LIBA_ASSERT(k != entries.end());
			}
		}
	}
}
	
flow::EntryDefinition* Manager::CreateStartEntry()
{
	Entries::const_iterator i =	this->entries.find(this->start);
	
	if (i != this->entries.end())
	{
		for (std::vector<TriggerEntry*>::iterator j = triggers.begin(); j != triggers.end(); ++j)
			(*j)->ScreenChanging(i->second);
		
		pushEntry(i->second);
		return advance();
	}
	else
	{
		LIBA_FAIL("Couldn't locate start entry");
		return NULL;
	}
}

bool Manager::on_end_node(xml::Provider * prov)
{
#ifdef DEBUG
	checkLinks(this->entries);
#endif

	return true;
}

namespace {
bool nameIs(std::string name, EntryExitDefinition* exit)
{
	return exit->GetName() == name;
}
} // anonymous namespace

flow::EntryDefinition* Manager::SwitchScreen(flow::EntryDefinition* currentEntry, const Key& exitId)
{
	if (!currentEntry)
	{
		LIBA_FAIL("We have troubles with screen flow configuration");
		return NULL;
	}

	doSwitchScreen(currentEntry, exitId);
	return advance();
}

void Manager::doSwitchScreen(EntryDefinition* currentEntry, const Key& exitId)
{
	std::vector<EntryExitDefinition*>::iterator i =
		std::find_if(
			currentEntry->ExitsBegin(), 
			currentEntry->ExitsEnd(), 
			boost::bind(&nameIs, exitId, _1));

	if (i == currentEntry->ExitsEnd())
	{
		LIBA_FAIL("Unknown exit name");
		return;
	}

	std::string entry = (*i)->GetEntry();

	if (!entry.empty() && entry[0] == '#')
	{
		switchSpecialScreen(entry);
		return;
	}

	if (!entry.empty() && entry[0] == '$')
	{
		queue.clear();
		entry.erase(0, 1);
	}

	Entries::const_iterator newEntry =
		this->entries.find(entry);
	
	if (newEntry == this->entries.end())
	{
		LIBA_FAIL("Cannot find named entry");
		return;
	}

	for (std::vector<TriggerEntry*>::iterator i = triggers.begin(); i != triggers.end(); ++i)
		(*i)->ScreenChanging(newEntry->second);

	pushEntry(newEntry->second);
}

void Manager::switchSpecialScreen(const Key& exitId)
{
	if (exitId == "#Advance")
	{
		return; // Do nothing. Succeeding advance() call will work for us
	}

	LIBA_FAIL("Cannot advance screen since queue is empty");
}

void Manager::pushEntry(flow::EntryDefinition* entry)
{
	if (entry->GetSelectorClass().empty())
	{
		queue.push_front(entry);
		return;
	}

	std::string newExitId;
	{
		std::auto_ptr<Selector> selector = 
			this->selectorFactory.Create(entry->GetSelectorClass());
		if (!selector.get())
		{
			LIBA_FAIL("Selector factory couldn't create needed selector");
			return;
		}

		selector->SetServiceProvider(*this->root);
		newExitId = selector->Select();
	}

	this->doSwitchScreen(entry, newExitId); // Tail recursion	
}

flow::EntryDefinition* Manager::advance()
{
	if (queue.empty())
	{
		LIBA_FAIL("Cannot advance when queue is empty");
		return NULL;
	}

	flow::EntryDefinition * nextEntry = queue.back();
	queue.pop_back();
	return nextEntry;
}

} // namespace flow
ARELLO_GAME_NAMESPACE_DECL_END